/*-------------------------------------------------------------------------
 *
 * slab.c
 *	  SLAB 分配器定义。
 *
 * SLAB 是一个 MemoryContext 实现，旨在处理大量相同大小对象的分配（和释放）。
 *
 *
 * Portions Copyright (c) 2017-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/mmgr/slab.c
 *
 *
 * 注意：
 *	常量分配大小允许在更通用的分配器上进行显著简化和各种优化。块被切割成正好
 *	合适大小（加上对齐）的块，不浪费任何内存。
 *
 *	空闲块的信息在块级别和全局（上下文）级别维护。这是可能的，因为块的大小（因此每块的
 *	块数）是固定的。
 *
 *	在每个块上，空闲块通过一个简单的链表进行跟踪。空闲块的内容被替换为下一个空闲块的
 *	索引，形成了一个非常简单的链表。每个块还包含一个空闲块的计数器。结合局部块级空闲列表，
 *	这使得最终释放整个块变得非常简单。
 *
 *	在上下文级别，我们使用 'freelist' 来跟踪按空闲块数量排序的块，从只有一个已分配块的块开始，
 *	而完全满的块则在尾部。
 *
 *	这也允许各种优化 - 例如在搜索空闲块时，分配器首先重用容量最多的块，期望一些
 *	不满的块将被完全清空（并返回操作系统）。
 *
 *	对于每个块，我们维护指向第一个空闲块的指针 - 这非常便宜，并且可以让我们跳过
 *	所有前面的已使用块，消除许多常见使用模式中的大量查找。在最坏的情况下，这样做
 *	的效果就像没有维护指针一样。
 *
 *	我们缓存空闲块数量最少的块的空闲列表索引（minFreeChunks），以便我们不必在每个
 *	SlabAlloc() 调用时搜索空闲列表，这相当昂贵。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "lib/ilist.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"

#define Slab_BLOCKHDRSZ	MAXALIGN(sizeof(SlabBlock))

/*
 * SlabContext 是 MemoryContext 的一种专业实现。
 */
typedef struct SlabContext
{
	MemoryContextData header;	/* 标准内存上下文字段 */
	/* 此上下文的分配参数： */
	Size		chunkSize;		/* 块大小 */
	Size		fullChunkSize;	/* 包括头和对齐的块大小 */
	Size		blockSize;		/* 块大小 */
	Size		headerSize;		/* 上下文头的已分配大小 */
	int			chunksPerBlock; /* 每个块的块数量 */
	int			minFreeChunks;	/* 任何块中最小的空闲块数量 */
	int			nblocks;		/* 已分配的块数量 */
#ifdef MEMORY_CONTEXT_CHECKING
	bool	   *freechunks;		/* 块中空闲块的位图 */
#endif
	/* 有空闲空间的块，按空闲块的数量分组： */
	dlist_head	freelist[FLEXIBLE_ARRAY_MEMBER];
} SlabContext;

/*
 * SlabBlock
 *		SLAB 分配器中单个块的结构。
 *
 * node: 全局空闲列表中的双向链表块
 * nfree: 此块中的空闲块数量
 * firstFreeChunk: 第一个空闲块的索引
 */
typedef struct SlabBlock
{
	dlist_node	node;			/* 双向链表 */
	int			nfree;			/* 空闲块的数量 */
	int			firstFreeChunk; /* 块中第一个空闲块的索引 */
} SlabBlock;

/*
 * SlabChunk
 *		SlabBlock 中每块内存的前缀
 *
 * 注意：为了满足内存上下文 API，块的负载区域必须
 * 是最大对齐的，并且“slab”链接必须紧邻负载区域
 * （参见 GetMemoryChunkContext）。由于我们不支持任何
 * 在其中 MAXALIGN 大于 2 倍 sizeof(void *) 的机器，
 * 所以在这个结构声明中没有任何特殊的黑客行为。但是
 * 下面有一个静态断言，确保对齐是正确的。
 */
typedef struct SlabChunk
{
	SlabBlock  *block;			/* 拥有此块的块 */
	SlabContext *slab;			/* 所属上下文 */
	/* 此处不得有填充以达到 MAXALIGN 边界！ */
} SlabChunk;


#define SlabPointerGetChunk(ptr)	\
	((SlabChunk *)(((char *)(ptr)) - sizeof(SlabChunk)))
#define SlabChunkGetPointer(chk)	\
	((void *)(((char *)(chk)) + sizeof(SlabChunk)))
#define SlabBlockGetChunk(slab, block, idx) \
	((SlabChunk *) ((char *) (block) + Slab_BLOCKHDRSZ	\
					+ (idx * slab->fullChunkSize)))
#define SlabBlockStart(block)	\
	((char *) block + Slab_BLOCKHDRSZ)
#define SlabChunkIndex(slab, block, chunk)	\
	(((char *) chunk - SlabBlockStart(block)) / slab->fullChunkSize)

/*
 * 这些函数实现了 Slab 上下文的 MemoryContext API。
 */
static void *fc_SlabAlloc(MemoryContext fc_context, Size fc_size);
static void fc_SlabFree(MemoryContext fc_context, void *fc_pointer);
static void *fc_SlabRealloc(MemoryContext fc_context, void *fc_pointer, Size fc_size);
static void fc_SlabReset(MemoryContext fc_context);
static void fc_SlabDelete(MemoryContext fc_context);
static Size fc_SlabGetChunkSpace(MemoryContext fc_context, void *fc_pointer);
static bool fc_SlabIsEmpty(MemoryContext fc_context);
static void fc_SlabStats(MemoryContext fc_context,
					  MemoryStatsPrintFunc fc_printfunc, void *fc_passthru,
					  MemoryContextCounters *fc_totals,
					  bool fc_print_to_stderr);
#ifdef MEMORY_CONTEXT_CHECKING
static void fc_SlabCheck(MemoryContext fc_context);
#endif

/*
 * 这是 Slab 上下文的虚拟函数表。
 */
static const MemoryContextMethods SlabMethods = {
	fc_SlabAlloc,
	fc_SlabFree,
	fc_SlabRealloc,
	fc_SlabReset,
	fc_SlabDelete,
	fc_SlabGetChunkSpace,
	fc_SlabIsEmpty,
	fc_SlabStats
#ifdef MEMORY_CONTEXT_CHECKING
	,fc_SlabCheck
#endif
};


/*
 * SlabContextCreate
 *		创建一个新的 Slab 上下文。
 *
 * parent: 父上下文，如果为顶层上下文则为 NULL
 * name: 上下文的名称（必须是静态分配）
 * blockSize: 分配块大小
 * chunkSize: 分配块大小
 *
 * chunkSize 不能超过：
 *		MAXALIGN_DOWN(SIZE_MAX) - MAXALIGN(Slab_BLOCKHDRSZ) - sizeof(SlabChunk)
 */
MemoryContext SlabContextCreate(MemoryContext fc_parent,
				  const char *fc_name,
				  Size fc_blockSize,
				  Size fc_chunkSize)
{
	int			fc_chunksPerBlock;
	Size		fc_fullChunkSize;
	Size		fc_freelistSize;
	Size		fc_headerSize;
	SlabContext *fc_slab;
	int			fc_i;

	/* 断言我们正确地填充了 SlabChunk */
	StaticAssertStmt(sizeof(SlabChunk) == MAXALIGN(sizeof(SlabChunk)),
					 "sizeof(SlabChunk) is not maxaligned");
	StaticAssertStmt(offsetof(SlabChunk, slab) + sizeof(MemoryContext) ==
					 sizeof(SlabChunk),
					 "padding calculation in SlabChunk is wrong");

	/* 确保链表节点适合于一个已释放的块 */
	if (fc_chunkSize < sizeof(int))
		fc_chunkSize = sizeof(int);

	/* 块，包括 SLAB 头（两个地址都很好对齐） */
	fc_fullChunkSize = sizeof(SlabChunk) + MAXALIGN(fc_chunkSize);

	/* 确保该块至少可以存储一个块。 */
	if (fc_blockSize < fc_fullChunkSize + Slab_BLOCKHDRSZ)
		elog(ERROR, "block size %zu for slab is too small for %zu chunks",
			 fc_blockSize, fc_chunkSize);

	/* 计算每个块的最大块数 */
	fc_chunksPerBlock = (fc_blockSize - Slab_BLOCKHDRSZ) / fc_fullChunkSize;

	/* 自由列表从 0 开始，结束于 chunksPerBlock。 */
	fc_freelistSize = sizeof(dlist_head) * (fc_chunksPerBlock + 1);

	/*
	 * 分配上下文头部。与 aset.c 不同，我们从不尝试将
	 * 其与第一个常规块组合；这没有额外的复杂性值得。
	 */

	/* 内存上下文头部的大小 */
	fc_headerSize = offsetof(SlabContext, freelist) + fc_freelistSize;

#ifdef MEMORY_CONTEXT_CHECKING

	/*
	 * 在内存检查下，我们需要为空闲块的位图分配额外空间。
	 * 位图是一个布尔数组，因此我们不需要担心对齐。
	 */
	fc_headerSize += fc_chunksPerBlock * sizeof(bool);
#endif

	fc_slab = (SlabContext *) malloc(fc_headerSize);
	if (fc_slab == NULL)
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed while creating memory context \"%s\".",
						   fc_name)));
#ifdef FDD //cppcheck
		return NULL;
#endif
	}

	/*
	 * 避免编写可能在这里和MemoryContextCreate之间失败的代码；
	 * 如果我们在这个过程中报告错误，将导致标题泄漏。
	 */

	/* 填充 SlabContext 特定的头部字段 */
	fc_slab->chunkSize = fc_chunkSize;
	fc_slab->fullChunkSize = fc_fullChunkSize;
	fc_slab->blockSize = fc_blockSize;
	fc_slab->headerSize = fc_headerSize;
	fc_slab->chunksPerBlock = fc_chunksPerBlock;
	fc_slab->minFreeChunks = 0;
	fc_slab->nblocks = 0;

	/* 初始化自由列表槽 */
	for (fc_i = 0; fc_i < (fc_slab->chunksPerBlock + 1); fc_i++)
		dlist_init(&fc_slab->freelist[fc_i]);

#ifdef MEMORY_CONTEXT_CHECKING
	/* 将 freechunks 指针设置在自由列表数组的后面 */
	fc_slab->freechunks
		= (bool *) fc_slab + offsetof(SlabContext, freelist) + fc_freelistSize;
#endif

	/* 最后，执行上下文创建的类型无关部分 */
	MemoryContextCreate((MemoryContext) fc_slab,
						T_SlabContext,
						&SlabMethods,
						fc_parent,
						fc_name);

	return (MemoryContext) fc_slab;
}

/*
 * SlabReset
 *		释放在给定集中过去分配的所有内存。
 *
 * 代码简单地释放上下文中的所有块 - 我们不保留任何
 * 保留块或类似的东西。
 */
static void fc_SlabReset(MemoryContext fc_context)
{
	int			fc_i;
	SlabContext *fc_slab = castNode(SlabContext, fc_context);

	Assert(fc_slab);

#ifdef MEMORY_CONTEXT_CHECKING
	/* 在释放前检查损坏和泄漏 */
	fc_SlabCheck(fc_context);
#endif

	/* 遍历自由列表并释放块 */
	for (fc_i = 0; fc_i <= fc_slab->chunksPerBlock; fc_i++)
	{
		dlist_mutable_iter fc_miter;

		dlist_foreach_modify(fc_miter, &fc_slab->freelist[fc_i])
		{
			SlabBlock  *fc_block = dlist_container(SlabBlock, node, fc_miter.cur);

			dlist_delete(fc_miter.cur);

#ifdef CLOBBER_FREED_MEMORY
			wipe_mem(fc_block, fc_slab->blockSize);
#endif
			free(fc_block);
			fc_slab->nblocks--;
			fc_context->mem_allocated -= fc_slab->blockSize;
		}
	}

	fc_slab->minFreeChunks = 0;

	Assert(fc_slab->nblocks == 0);
	Assert(fc_context->mem_allocated == 0);
}

/*
 * SlabDelete
 *		释放在给定上下文中分配的所有内存。
 */
static void fc_SlabDelete(MemoryContext fc_context)
{
	/* 重置以释放所有 SlabBlocks */
	fc_SlabReset(fc_context);
	/* 并释放上下文头部 */
	free(fc_context);
}

/*
 * SlabAlloc
 *		返回分配的给定大小内存的指针，如果
 *		请求无法完成，则返回 NULL；内存被添加到 slab 中。
 */
static void * fc_SlabAlloc(MemoryContext fc_context, Size fc_size)
{
	SlabContext *fc_slab = castNode(SlabContext, fc_context);
	SlabBlock  *fc_block;
	SlabChunk  *fc_chunk;
	int			fc_idx;

	Assert(fc_slab);

	Assert((fc_slab->minFreeChunks >= 0) &&
		   (fc_slab->minFreeChunks < fc_slab->chunksPerBlock));

	/* 确保我们只允许正确的请求大小 */
	if (fc_size != fc_slab->chunkSize)
		elog(ERROR, "unexpected alloc chunk size %zu (expected %zu)",
			 fc_size, fc_slab->chunkSize);

	/*
	 * 如果在任何现有块中没有空闲块，则创建一个新块
	 * 并将其放到最后一个自由列表桶中。
	 *
	 * slab->minFreeChunks == 0 意味着没有块具有空闲块，
	 * 这要归功于 minFreeChunks 在 SlabAlloc() 结束时的更新。
	 */
	if (fc_slab->minFreeChunks == 0)
	{
		fc_block = (SlabBlock *) malloc(fc_slab->blockSize);

		if (fc_block == NULL)
			return NULL;

		fc_block->nfree = fc_slab->chunksPerBlock;
		fc_block->firstFreeChunk = 0;

		/*
		 * 将所有块放在自由列表上。遍历块并将每个
		 * 块指向下一个块。
		 */
		for (fc_idx = 0; fc_idx < fc_slab->chunksPerBlock; fc_idx++)
		{
			fc_chunk = SlabBlockGetChunk(fc_slab, fc_block, fc_idx);
			*(int32 *) SlabChunkGetPointer(fc_chunk) = (fc_idx + 1);
		}

		/*
		 * 并将其添加到最后一个自由列表中，所有块都为空。
		 *
		 * 我们知道自由列表中没有块，否则我们就不需要
		 * 新块了。
		 */
		Assert(dlist_is_empty(&fc_slab->freelist[fc_slab->chunksPerBlock]));

		dlist_push_head(&fc_slab->freelist[fc_slab->chunksPerBlock], &fc_block->node);

		fc_slab->minFreeChunks = fc_slab->chunksPerBlock;
		fc_slab->nblocks += 1;
		fc_context->mem_allocated += fc_slab->blockSize;
	}

	/* 从自由列表中获取块（即使新块也在其中） */
	fc_block = dlist_head_element(SlabBlock, node,
							   &fc_slab->freelist[fc_slab->minFreeChunks]);

	/* 确保我们实际获得了一个有效的块，并且匹配 nfree */
	Assert(fc_block != NULL);
	Assert(fc_slab->minFreeChunks == fc_block->nfree);
	Assert(fc_block->nfree > 0);

	/* 我们知道块中第一个空闲块的索引 */
	fc_idx = fc_block->firstFreeChunk;

	/* 确保块索引有效，并且标记为空闲 */
	Assert((fc_idx >= 0) && (fc_idx < fc_slab->chunksPerBlock));

	/* 计算块的chunk位置块起始（在块头之后） */
	fc_chunk = SlabBlockGetChunk(fc_slab, fc_block, fc_idx);

	/*
	 * 更新块 nfree 计数，以及 minFreeChunks，因为我们
	 * 减少了具有最小空闲块数的块的 nfree
	 * （因为这就是我们选择块的方式）。
	 */
	fc_block->nfree--;
	fc_slab->minFreeChunks = fc_block->nfree;

	/*
	 * 从自由列表头中删除块。下一个空闲块的索引存储在块本身中。
	 */
	VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(fc_chunk), sizeof(int32));
	fc_block->firstFreeChunk = *(int32 *) SlabChunkGetPointer(fc_chunk);

	Assert(fc_block->firstFreeChunk >= 0);
	Assert(fc_block->firstFreeChunk <= fc_slab->chunksPerBlock);

	Assert((fc_block->nfree != 0 &&
			fc_block->firstFreeChunk < fc_slab->chunksPerBlock) ||
		   (fc_block->nfree == 0 &&
			fc_block->firstFreeChunk == fc_slab->chunksPerBlock));

	/* 将整个块移动到自由列表中的正确位置 */
	dlist_delete(&fc_block->node);
	dlist_push_head(&fc_slab->freelist[fc_block->nfree], &fc_block->node);

	/*
	 * 最后更新 minFreeChunks，即对具有
	 * 最少空闲块数的块的索引。我们只需在块
	 * 变满时执行此操作（否则我们知道当前块就是正确的）。
	 * 我们将遍历自由列表，直到找到一个非空条目。
	 */
	if (fc_slab->minFreeChunks == 0)
	{
		for (fc_idx = 1; fc_idx <= fc_slab->chunksPerBlock; fc_idx++)
		{
			if (dlist_is_empty(&fc_slab->freelist[fc_idx]))
				continue;

			/* 找到一个非空的自由列表 */
			fc_slab->minFreeChunks = fc_idx;
			break;
		}
	}

	if (fc_slab->minFreeChunks == fc_slab->chunksPerBlock)
		fc_slab->minFreeChunks = 0;

	/* 准备初始化块头。 */
	VALGRIND_MAKE_MEM_UNDEFINED(fc_chunk, sizeof(SlabChunk));

	fc_chunk->block = fc_block;
	fc_chunk->slab = fc_slab;

#ifdef MEMORY_CONTEXT_CHECKING
	/* slab标记以捕捉“未使用”空间的冲突 */
	if (fc_slab->chunkSize < (fc_slab->fullChunkSize - sizeof(SlabChunk)))
	{
		set_sentinel(SlabChunkGetPointer(fc_chunk), fc_size);
		VALGRIND_MAKE_MEM_NOACCESS(((char *) fc_chunk) +
								   sizeof(SlabChunk) + fc_slab->chunkSize,
								   fc_slab->fullChunkSize -
								   (fc_slab->chunkSize + sizeof(SlabChunk)));
	}
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
	/* 用垃圾填充分配的空间 */
	randomize_mem((char *) SlabChunkGetPointer(fc_chunk), fc_size);
#endif

	Assert(fc_slab->nblocks * fc_slab->blockSize == fc_context->mem_allocated);

	return SlabChunkGetPointer(fc_chunk);
}

/*
 * SlabFree
 *		释放已分配的内存；内存从slab中移除。
 */
static void fc_SlabFree(MemoryContext fc_context, void *fc_pointer)
{
	int			fc_idx;
	SlabContext *fc_slab = castNode(SlabContext, fc_context);
	SlabChunk  *fc_chunk = SlabPointerGetChunk(fc_pointer);
	SlabBlock  *fc_block = fc_chunk->block;

#ifdef MEMORY_CONTEXT_CHECKING
	/* 测试是否有人在块的未使用空间上乱写 */
	if (fc_slab->chunkSize < (fc_slab->fullChunkSize - sizeof(SlabChunk)))
		if (!sentinel_ok(fc_pointer, fc_slab->chunkSize))
			elog(WARNING, "detected write past chunk end in %s %p",
				 fc_slab->header.name, fc_chunk);
#endif

	/* 计算块开始处的片段索引 */
	fc_idx = SlabChunkIndex(fc_slab, fc_block, fc_chunk);

	/* 将片段添加到自由列表，并更新块的nfree计数 */
	*(int32 *) fc_pointer = fc_block->firstFreeChunk;
	fc_block->firstFreeChunk = fc_idx;
	fc_block->nfree++;

	Assert(fc_block->nfree > 0);
	Assert(fc_block->nfree <= fc_slab->chunksPerBlock);

#ifdef CLOBBER_FREED_MEMORY
	/* XXX 不要擦除int32索引，用于块级自由列表 */
	wipe_mem((char *) fc_pointer + sizeof(int32),
			 fc_slab->chunkSize - sizeof(int32));
#endif

	/* 从自由列表中移除块 */
	dlist_delete(&fc_block->node);

	/*
	 * 查看我们是否需要更新slab的minFreeChunks字段 - 只有在
	 * 我们释放一个块之前，该块才有该数量的空闲块时才需要这样做。
	 * 在这种情况下，我们检查原始自由列表中是否仍有块，
	 * 如果仍有块则保持当前值（如果仍然有块），否则将其加1
	 * （新的块仍然是具有最小空闲块的块）。
	 *
	 * 唯一的例外是当块将被完全释放 - 在这种情况下我们将释放它，
	 * 因此不能将其用于minFreeChunks。然而，这意味着没有更多的
	 * 块具有空闲块。
	 */
	if (fc_slab->minFreeChunks == (fc_block->nfree - 1))
	{
		/* 我们是否从自由列表中移除了最后一个片段？ */
		if (dlist_is_empty(&fc_slab->freelist[fc_slab->minFreeChunks]))
		{
			/* 但如果我们使块完全免费，我们将释放它 */
			if (fc_block->nfree == fc_slab->chunksPerBlock)
				fc_slab->minFreeChunks = 0;
			else
				fc_slab->minFreeChunks++;
		}
	}

	/* 如果块现在完全空，则释放它。 */
	if (fc_block->nfree == fc_slab->chunksPerBlock)
	{
		free(fc_block);
		fc_slab->nblocks--;
		fc_context->mem_allocated -= fc_slab->blockSize;
	}
	else
		dlist_push_head(&fc_slab->freelist[fc_block->nfree], &fc_block->node);

	Assert(fc_slab->nblocks >= 0);
	Assert(fc_slab->nblocks * fc_slab->blockSize == fc_context->mem_allocated);
}

/*
 * SlabRealloc
 *		改变片段的已分配大小。
 *
 * 由于Slab设计用于分配相同大小的内存块，因此不能进行实际的
 * 块大小更改。我们尝试温和处理，并允许调用相同大小，这是因为
 * 在这种情况下我们可以简单地返回相同的块。当大小不同时，
 * 我们将抛出错误。
 *
 * 我们也可以允许大小 < chunkSize的请求。然而，这似乎相当
 * 没有意义 - Slab旨在处理恒定大小的块，此外realloc通常
 * 用于放大块。
 */
static void * fc_SlabRealloc(MemoryContext fc_context, void *fc_pointer, Size fc_size)
{
	SlabContext *fc_slab = castNode(SlabContext, fc_context);

	Assert(fc_slab);

	/* 无法使用slab进行实际的realloc，但我们尽量温和处理 */
	if (fc_size == fc_slab->chunkSize)
		return fc_pointer;

	elog(ERROR, "slab allocator does not support realloc()");
	return NULL;				/* 保持编译器安静 */
}

/*
 * SlabGetChunkSpace
 *		给定一个当前分配的块，确定它占用的总空间
 *		（包括所有内存分配开销）。
 */
static Size fc_SlabGetChunkSpace(MemoryContext fc_context, void *fc_pointer)
{
	SlabContext *fc_slab = castNode(SlabContext, fc_context);

	Assert(fc_slab);

	return fc_slab->fullChunkSize;
}

/*
 * SlabIsEmpty
 *		Slab是否没有分配空间？
 */
static bool fc_SlabIsEmpty(MemoryContext fc_context)
{
	SlabContext *fc_slab = castNode(SlabContext, fc_context);

	Assert(fc_slab);

	return (fc_slab->nblocks == 0);
}

/*
 * SlabStats
 *		计算关于Slab上下文内存消耗的统计信息。
 *
 * printfunc: 如果不为NULL，则将可读的统计信息字符串传递给此。
 * passthru: 将此指针直接传递给printfunc。
 * totals: 如果不为NULL，将此上下文的统计信息添加到*totals中。
 * print_to_stderr: 如果为true则将统计信息打印到stderr，否则打印到elog。
 */
static void fc_SlabStats(MemoryContext fc_context,
		  MemoryStatsPrintFunc fc_printfunc, void *fc_passthru,
		  MemoryContextCounters *fc_totals,
		  bool fc_print_to_stderr)
{
	SlabContext *fc_slab = castNode(SlabContext, fc_context);
	Size		fc_nblocks = 0;
	Size		fc_freechunks = 0;
	Size		fc_totalspace;
	Size		fc_freespace = 0;
	int			fc_i;

	/* 将上下文头包含在 totalspace 中 */
	fc_totalspace = fc_slab->headerSize;

	for (fc_i = 0; fc_i <= fc_slab->chunksPerBlock; fc_i++)
	{
		dlist_iter	fc_iter;

		dlist_foreach(fc_iter, &fc_slab->freelist[fc_i])
		{
			SlabBlock  *fc_block = dlist_container(SlabBlock, node, fc_iter.cur);

			fc_nblocks++;
			fc_totalspace += fc_slab->blockSize;
			fc_freespace += fc_slab->fullChunkSize * fc_block->nfree;
			fc_freechunks += fc_block->nfree;
		}
	}

	if (fc_printfunc)
	{
		char		fc_stats_string[200];

		snprintf(fc_stats_string, sizeof(fc_stats_string),
				 "%zu total in %zu blocks; %zu free (%zu chunks); %zu used",
				 fc_totalspace, fc_nblocks, fc_freespace, fc_freechunks,
				 fc_totalspace - fc_freespace);
		fc_printfunc(fc_context, fc_passthru, fc_stats_string, fc_print_to_stderr);
	}

	if (fc_totals)
	{
		fc_totals->nblocks += fc_nblocks;
		fc_totals->freechunks += fc_freechunks;
		fc_totals->totalspace += fc_totalspace;
		fc_totals->freespace += fc_freespace;
	}
}


#ifdef MEMORY_CONTEXT_CHECKING

/*
 * SlabCheck
 *		遍历片段并检查内存的一致性。
 *
 * 注意：将错误报告为WARNING，而不是ERROR或FATAL。
 * 否则，当出现问题时，您将发现自己陷入无限循环中，因为在
 * elog清理尝试释放内存时将再次进入此例程！
 */
static void fc_SlabCheck(MemoryContext fc_context)
{
	int			fc_i;
	SlabContext *fc_slab = castNode(SlabContext, fc_context);
	const char *fc_name = fc_slab->header.name;

	Assert(fc_slab);
	Assert(fc_slab->chunksPerBlock > 0);

	/* 遍历所有自由列表 */
	for (fc_i = 0; fc_i <= fc_slab->chunksPerBlock; fc_i++)
	{
		int			fc_j,
					fc_nfree;
		dlist_iter	fc_iter;

		/* 遍历此自由列表上的所有块 */
		dlist_foreach(fc_iter, &fc_slab->freelist[fc_i])
		{
			int			fc_idx;
			SlabBlock  *fc_block = dlist_container(SlabBlock, node, fc_iter.cur);

			/*
			 * 确保空闲块的数量（在块头中）与自由列表中的位置匹配。
			 */
			if (fc_block->nfree != fc_i)
				elog(WARNING, "problem in slab %s: number of free chunks %d in block %p does not match freelist %d",
					 fc_name, fc_block->nfree, fc_block, fc_i);

			/* 重置此块的空闲块位图 */
			memset(fc_slab->freechunks, 0, (fc_slab->chunksPerBlock * sizeof(bool)));
			fc_idx = fc_block->firstFreeChunk;

			/*
			 * 现在遍历块，计算空闲块并对已使用的块执行一些
			 * 附加检查。由于块自由列表存储在块本身中，我们必须
			 * 遍历块并构建我们自己的位图。
			 */

			fc_nfree = 0;
			while (fc_idx < fc_slab->chunksPerBlock)
			{
				SlabChunk  *fc_chunk;

				/* 将块计算为空闲，添加到位图中 */
				fc_nfree++;
				fc_slab->freechunks[fc_idx] = true;

				/* 读取下一个空闲块的索引 */
				fc_chunk = SlabBlockGetChunk(fc_slab, fc_block, fc_idx);
				VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(fc_chunk), sizeof(int32));
				fc_idx = *(int32 *) SlabChunkGetPointer(fc_chunk);
			}

			for (fc_j = 0; fc_j < fc_slab->chunksPerBlock; fc_j++)
			{
				/* 位图中的非零位表示块已被使用 */
				if (!fc_slab->freechunks[fc_j])
				{
					SlabChunk  *fc_chunk = SlabBlockGetChunk(fc_slab, fc_block, fc_j);

					/* 块同时具有块和slab指针，因此检查两者 */
					if (fc_chunk->block != fc_block)
						elog(WARNING, "problem in slab %s: bogus block link in block %p, chunk %p",
							 fc_name, fc_block, fc_chunk);

					if (fc_chunk->slab != fc_slab)
						elog(WARNING, "problem in slab %s: bogus slab link in block %p, chunk %p",
							 fc_name, fc_block, fc_chunk);

					/* 可能存在哨兵（由于对齐） */
					if (fc_slab->chunkSize < (fc_slab->fullChunkSize - sizeof(SlabChunk)))
						if (!sentinel_ok(fc_chunk, sizeof(SlabChunk) + fc_slab->chunkSize))
							elog(WARNING, "problem in slab %s: detected write past chunk end in block %p, chunk %p",
								 fc_name, fc_block, fc_chunk);
				}
			}

			/*
			 * 确保我们得到了预期数量的空闲块（如在块头中跟踪）。
			 */
			if (fc_nfree != fc_block->nfree)
				elog(WARNING, "problem in slab %s: number of free chunks %d in block %p does not match bitmap %d",
					 fc_name, fc_block->nfree, fc_block, fc_nfree);
		}
	}

	Assert(fc_slab->nblocks * fc_slab->blockSize == fc_context->mem_allocated);
}

#endif							/* MEMORY_CONTEXT_CHECKING */
